package naga.x.net.impl;

import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.mina.core.session.IoSession;

import naga.x.game.time.TimeUpdater;
import naga.x.net.Client;
import naga.x.net.Packet;
import naga.x.net.PacketHandler;
import naga.x.net.Session;
import naga.x.net.SessionObserver;
import naga.x.util.SessionUtils;

public class ClientSession implements Session {
	
	protected IoSession session;
	protected Client client;
	protected Queue<Packet> packets;
	protected PacketHandler handler;
	protected State state;
	protected SessionObserver observer;
	protected static final int HEARTBEAT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(20);
	
	public enum State {
		CONNECTED,
		CLOSING,
		CLOSED
	}
	
	public ClientSession(IoSession session, PacketHandler handler, SessionObserver observer) {
		this(session, null, handler, observer);
	}
	
	public ClientSession(IoSession session, Client client, PacketHandler handler, SessionObserver observer) {
		this.session = session;
		this.client = client;
		this.handler = handler;
		this.packets = new ArrayBlockingQueue<Packet>(1024);
		this.observer = observer;
		this.state = State.CONNECTED;
	}

	@Override
	public long getId() {
		return session.getId();
	}

	@Override
	public String getRemoteAddress() {
		String addr = getAttribute(SessionUtils.ADDRESS);
		return addr != null ? addr : String.valueOf(session.getRemoteAddress());
	}

	@Override
	public String getLocalAddress() {
		return String.valueOf(session.getLocalAddress());
	}

	@Override
	public <T> T getAttribute(Object key) {
		return (T) session.getAttribute(key);
	}
	
	public <T> T getAttribute(Object key, T defaultVal) {
		return (T) session.getAttribute(key, defaultVal);
	}

	@Override
	public <T> T setAttribute(Object key, T value) {
		return (T) session.setAttribute(key, value);
	}

	@Override
	public <T> T removeAttribute(Object key) {
		return (T) session.removeAttribute(key);
	}

	@Override
	public boolean isConnected() {
//		return session.isConnected();
		return state == State.CONNECTED;
	}

	@Override
	public boolean isClosing() {
//		return session.isClosing();
		return state == State.CLOSING;
	}

	@Override
	public void close(boolean immediately) {
		if (isConnected()) {
			state = State.CLOSING;
		}
		if (session.isConnected() && !session.isClosing()) {
			if (immediately) {
				session.closeNow();
			} else {
				session.closeOnFlush();
			}
		}
	}

	@Override
	public <C extends Client> C getClient() {
		return (C) client;
	}

	@Override
	public <C extends Client> void setClient(C client) {
		this.client = client;
		if (client != null) {
			client.setSession(this);
		}
	}

	@Override
	public void received(Packet packet) {
		packets.offer(packet);
	}

	@Override
	public void send(Packet packet) {
		if (isConnected()) {
			session.write(packet);
		}
	}

	@Override
	public boolean update() {
		if (isClosing()) {
			state = State.CLOSED;
			observer.fireSessionClosed(this);
			dispose();
			return false;
		}
		if (!isConnected()) {
			return true;
		}
		if (HEARTBEAT_TIMEOUT > 0) {
			Long now = TimeUpdater.getInstance().now();
			Long lastHeartbeatTime = getAttribute(SessionUtils.LAST_HEARTBEAT_TIME, now);
			if (now - lastHeartbeatTime >= HEARTBEAT_TIMEOUT) {
				// 心跳超时
				close(false);
				return true;
			}
		}
		if (packets.isEmpty()) {
			return true;
		}
		Packet packet;
		while ((packet = packets.poll()) != null) {
			handler.handle(this, packet);
		}
		return true;
	}

	@Override
	public void dispose() {
		packets.clear();
		Set<Object> keys = session.getAttributeKeys();
		for (Object key : keys) {
			session.removeAttribute(key);
		}
	}
	

}
